﻿@* Copyright (c) Kurrent, Inc and/or licensed to Kurrent, Inc under one or more agreements. *@
@* Kurrent, Inc licenses this file to you under the Kurrent License v1 (see LICENSE.md). *@

@page "/ui/query"
@attribute [Authorize(Roles = "$admins")]
@attribute [ExcludeFromInteractiveRouting]
@rendermode InteractiveServer
@using System.Diagnostics
@using BlazorMonaco
@using BlazorMonaco.Editor
@using Dapper
@using Kurrent.Quack.ConnectionPool
@using KurrentDB.Common.Configuration
@using KurrentDB.Components.Plugins
@using KurrentDB.Components.Tools
@using KurrentDB.UI.Services
@using Microsoft.AspNetCore.Authorization
@using Serilog
@inject DuckDBConnectionPool Db
@inject Preferences Preferences
@inject IDialogService DialogService
@inject PluginsService Plugins

<MudPopoverProvider/>
<MudDialogProvider />

<PageTitle>KurrentDB: Query</PageTitle>

@if (!Plugins.IsPluginEnabled(PluginNames.SecondaryIndexes)) {
	<NotEnabled Plugin="@PluginNames.SecondaryIndexes"/>
	return;
}

<MudCard Outlined="true">
	<MudCardContent>
		<MudAlert Severity="Severity.Warning">
			<h3 class="mb-2">Experimental feature</h3>
			<p>Querying large datasets may cause a performance impact. Consider adding <code>LIMIT</code> to your query.</p>
		</MudAlert>
		<MudText class="my-4">Type SQL statement in the editor below</MudText>
		<StandaloneCodeEditor @ref="_editor" Id="query-editor"
		                      OnDidInit="OnEditorInit"
		                      ConstructionOptions="EditorConstructionOptions"/>
		@if (!string.IsNullOrEmpty(_message)) {
			<MudAlert Severity="Severity.Normal">@_message</MudAlert>
		}
		@if (!string.IsNullOrEmpty(_error)) {
			<MudAlert Severity="Severity.Error">@_error</MudAlert>
		}
	</MudCardContent>
	<MudCardActions>
		<MudButton Variant="Variant.Text" Color="Color.Primary" OnClick="Execute">Run</MudButton>
		<MudIconButton Icon="@Icons.Material.Filled.Help" Color="Color.Default" OnClick="ShowHelp">Help</MudIconButton>
	</MudCardActions>
</MudCard>
<MudOverlay Visible="_showProgress" DarkBackground="true" Absolute="true">
	<MudProgressCircular Color="Color.Secondary" Indeterminate="true"/>
</MudOverlay>

@if (_items != null) {
	<MudDataGrid Items="_items" T="IDictionary<string, object>">
		<Columns>
			<HierarchyColumn T="IDictionary<string, object>" ButtonDisabledFunc="HasNoJson"/>
			@if (!_items.Any()) {
			}
			else {
				foreach (var item in _items.First().Keys) {
					<PropertyColumn Property="x => Shorten(x[item])" Title="@item"></PropertyColumn>
				}
			}
		</Columns>
		<ChildRowContent>
			<MudCard>
				<MudCardContent>
					@foreach (var item in context.Item.Keys) {
						if (!IsJsonString(context.Item[item])) continue;

						<MudText Typo="Typo.h6">@item</MudText>

						<Json JsonString="@context.Item[item]?.ToString()"></Json>
					}
				</MudCardContent>
			</MudCard>
		</ChildRowContent>
		<PagerContent>
			<MudDataGridPager T="IDictionary<string, object>"/>
		</PagerContent>
	</MudDataGrid>
}

@code {

	static readonly ILogger Log = Serilog.Log.ForContext<Query>();
	IEnumerable<IDictionary<string, object>> _items;
	StandaloneCodeEditor _editor = null!;
	string _message = "";
	string _error = "";
	bool _showProgress;

	StandaloneEditorConstructionOptions EditorConstructionOptions(StandaloneCodeEditor arg) => new() {
		AutomaticLayout = false,
		Language = "sql",
		Theme = EditorTheme(),
		FontSize = 16
	};

	readonly DialogOptions _maxWidth = new() { MaxWidth = MaxWidth.Medium, FullWidth = true };

	async Task ShowHelp() {
		await DialogService.ShowAsync<QueryHelpDialog>("How to use the query engine", _maxWidth);
	}

	async Task OnEditorInit() {
		await _editor.AddCommand((int)KeyMod.CtrlCmd | (int)KeyCode.Enter, _ => InvokeAsync(Execute));
		await _editor.Focus();
	}

	async Task Execute() {
		var sql = await _editor.GetValue();

		if (string.IsNullOrWhiteSpace(sql)) return;

		_items = null;
		_showProgress = true;
		_message = "";
		_error = "";
		StateHasChanged();

		var watch = new Stopwatch();
		watch.Start();

		try {
			_items = Db.ExecuteAdHocUserQuery(sql);
			watch.Stop();
			_message = $"Query executed in {watch}";
		}
		catch (Exception e) {
			_error = e.Message;
		}
		finally {
			_showProgress = false;
			StateHasChanged();
		}
	}

	protected override void OnInitialized() {
		base.OnInitialized();
		Preferences.ThemeChanged += PreferencesOnThemeChanged;

		return;

		void PreferencesOnThemeChanged(object sender, EventArgs e) => _editor.UpdateOptions(new() { Theme = EditorTheme() });
	}

	static object Shorten(object value) => value is not string s ? value : s[..Math.Min(s.Length, 50)];

	static bool HasNoJson(IDictionary<string, object> arg) => arg.Keys.Select(key => arg[key]).All(item => !IsJsonString(item));

	static bool IsJsonString(object value) => value is string s && (s.StartsWith('{') && s.EndsWith('}') || s.StartsWith('[') && s.EndsWith(']'));

	string EditorTheme() => Preferences.DarkMode ? "vs-dark" : "vs-light";
}

